/**
 * 通用安全的http请求插件
 * @author luwei
 * @version 1.0.0, 2019-03-16
 */
import axios from 'axios'
import qs from 'qs'
import moment from 'moment'
import store from '@/store'
// import { Notify } from 'vant'
import {
  Message
} from 'element-ui'
import _ from 'lodash'
import {
  encryptByAES,
  sha256,
  md5,
  base64,
  newAESKey,
  encryptByRSA,
  randomString,
  encryptBySM4
} from './crypto'

/** axios默认配置 */
let configAxios = {
  // baseURL: '无需配置，由vue自动代理, 见vue.config.js->devServer.proxy'
  baseURL: store.getters.baseApi,
  // Timeout
  timeout: 60 * 1000,
  withCredentials: true
}

/** 会话票据请求头名称 */
const HEADER_TOKEN_NAME = 'token'
/** 会话票据参数名称，在sessionStorage中存储的key名称 */
const TOKEN_NAME = 'token'
/** ajax请求工具实例 */
const _axios = axios.create(configAxios)

/** axios请求拦截器 */
_axios.interceptors.request.use(
  config => {
    let token = sessionStorage.getItem(TOKEN_NAME)
    if (_.isString(token)) {
      config.headers[HEADER_TOKEN_NAME] = token
    }
    console.log('token', token);
    return config
  },
  error => {
    Promise.reject(error)
  }
)

/** axios响应拦截器 */
_axios.interceptors.response.use(
  response => {
    return successCallback(response)
  },
  error => {
    // Do something with response error
    return errorCallback(error)
  }
)

/**
 * 错误回调函数
 * @param {*} error 错误对象
 */
const errorCallback = error => {
  if (error.response) {
    // The request was made and the server responded with a status code
    // that falls out of the range of 2xx
    if (error.response.data.code) {
      // 未登录
      if (error.code === 30001) {
        // sessionStorage.clear()
        // window.location.href = error.response.data.data
        toLoginPage(error.response.data.data)
      } else {
        Message({
          message: error.response.data.msg,
          type: 'warning'
        })
      }
    } else {
      Message({
        // 服务挂了
        message: '当前服务异常或网络异常, 请稍后再试',
        type: 'warning'
      })
    }
  } else if (error.request) {
    // The request was made but no response was received
    // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
    // http.ClientRequest in node.js
    Message({
      // 网络挂了或服务忙（超时未收到响应）
      message: '当前服务忙或网络异常, 请稍后再试',
      type: 'warning'
    })
  } else {
    // Something happened in setting up the request that triggered an Error
    Message({
      message: error.message,
      type: 'warning'
    })
  }
  return Promise.reject(error)
}

/**
 * 成功回调函数
 * @param {*} response 响应对象
 */
const successCallback = response => {
  if (response.status > 200) {
    return response.data
  }
  if (response.data.code === 20000) {
    return response.data
  }
  if (response.data.code === 20001) {
    // 由服务端控制的在ajax中进行的redirect跳转，data属性即为跳转的URL
    sessionStorage.removeItem('token')
    store.commit('setToken', null)
    // window.location.href = response.data.data
    toLoginPage(response.data.data)
    return
  }
  if (response.data.code === 30006) {
    // 登陆时的密钥策略校验不通过，必须修改密码
    return response.data
  }
  if (response.data.code >= 30000) {
    Message({
      message: response.data.msg ? response.data.msg : '操作失败',
      type: 'warning'
    })
  }
  return response.data
}

/**
 * 跳转到登录页面
 * @param {String} loginUrl 服务端配置的默认登录页面
 */
const toLoginPage = loginUrl => {
  console.log(window.location.href, loginUrl);
  // 历史页面
  sessionStorage.setItem('history_url', window.location.href)
  window.location.href = '/login?auto=true'
  // window.location.href = loginUrl
  // let loginPath = sessionStorage.getItem('loginPath')
  // if (loginPath) {
  //   // 指定了登录页面
  //   if ('/' === loginUrl.substr(loginUrl.length - 1)) {
  //     // loginUrl以/结尾
  //     window.location.href =
  //       loginUrl.substring(0, loginUrl.length - 1) + loginPath
  //   } else {
  //     window.location.href = loginUrl + loginPath
  //   }
  // } else {
  //   // 默认登录页面
  //   window.location.href = loginUrl
  // }
}

/**
 * 默认的安全等级: 可用值[0, 1, 2, 3]
 * 0:无, 1:防重放, 2:防重放+Base64编码+签名, 3:防重放+AES/RSA加密+签名
 */
const DEFAULT_SECURITY_CLASS = 1

/**
 * get方式请求, 使用默认安全等级DEFAULT_SECURITY_CLASS
 * @param {String} apiUri api uri
 * @param {Object} params 参数
 */
const get = (apiUri, params) => {
  return getSafe(apiUri, params, DEFAULT_SECURITY_CLASS)
}

/**
 * 安全get提交，暂时仅支持安全等级[0, 1]
 * @param {String} apiUri api uri
 * @param {Object} params 参数
 * @param {int} securityLevel 安全等级[0, 1], 如下：
 *    0:无, 1:防重放
 */
const getSafe = (apiUri, params, securityLevel = DEFAULT_SECURITY_CLASS) => {
  let _config = {
    params
  }

  let safeTag = null
  if (securityLevel !== 0) {
    safeTag = createSafeTag(1)
  }

  return _axios
    .get(apiUri, Object.assign(_config, createSafeConfig(safeTag)))
    .then(response => {
      return response
    })
    .catch(error => {
      return error
    })
}

/**
 * post方式请求, 表单传参, 使用默认安全等级DEFAULT_SECURITY_CLASS
 * @param {String} apiUri api uri
 * @param {ObjectOrJsonString} params 请求参数，对象或json字符串
 */
const post = (apiUri, params) => {
  return postSafe(apiUri, params, DEFAULT_SECURITY_CLASS)
}

/**
 * 兼容历史版本的方法
 * 服务端参数必须使用@RequestBody注解接收参数
 * @param {String} apiUri api uri
 * @param {ObjectOrJsonString} params 请求参数，对象或json字符串
 * @param {int} securityLevel 安全等级[0, 1, 2, 3], 默认为1, 如下：
 *    0:无, 1:防重放, 2:防重放+Base64编码+签名, 3:防重放+AES/RSA加密+签名
 */
const postJson = (apiUri, params, securityLevel = 1) => {
  if (_.isString(params)) {
    return postSafe(apiUri, params, securityLevel)
  } else {
    return postSafe(apiUri, JSON.stringify(params), securityLevel)
  }
}

/**
 * 安全post提交, 表单传参
 * 如果params是JsonString, 在使用安全等级[0, 1]时,
 * 服务端参数需要使用@RequestBody注解接收参数
 * @param {String} apiUri api uri
 * @param {ObjectOrJsonString} params 请求参数，对象或json字符串
 * @param {int} securityLevel 安全等级[0, 1, 2, 3], 默认为3, 如下：
 *    0:无, 1:防重放, 2:防重放+Base64编码+签名, 3:防重放+AES/RSA加密+签名
 */
const postSafe = (apiUri, params, securityLevel = 3) => {
  let ticket = sessionStorage.getItem(TOKEN_NAME)
  if (securityLevel < 0) {
    securityLevel = 0
  }
  if (securityLevel > 3) {
    securityLevel = 3
  }
  let safeTag = createSafeTag(securityLevel)

  switch (securityLevel) {
    case 0:
      // 无
      return postSafeOfSecurityLevel0(apiUri, params)
    case 1:
      // 防重放
      return postSafeOfSecurityLevel1(apiUri, params, safeTag)
    case 2:
      // 防重放+Base64编码+签名
      return postSafeOfSecurityLevel2(apiUri, params, safeTag, ticket || '')
    case 3:
      // 防重放+AES/RSA加密+签名
      return postSafeOfSecurityLevel3(apiUri, params, safeTag, ticket || '')
    default:
      // console.log('不存在该安全等级:' + securityLevel)
  }
}

/**
 * 0:无
 * @param {String} apiUri api uri
 * @param {ObjectOrJsonString} params 请求参数，对象或json字符串
 * @return {Promise} Promise
 */
const postSafeOfSecurityLevel0 = (apiUri, params) => {
  let isStr = _.isString(params)
  let _param = isStr ? params : qs.stringify(params)
  return _axios
    .post(apiUri, _param, createSafeConfig(null, isStr ? 2 : 1))
    .then(response => {
      return response
    })
    .catch(error => {
      return error
    })
}

/**
 * 1:防重放
 * @param {String} apiUri api uri
 * @param {ObjectOrJsonString} params 请求参数，对象或json字符串
 * @param {String} safeTag 安全标签, 安全等级;时间戳;防重放攻击的nonce, 例如：1;1551687809382;a411a616d438a...
 * @param {String} ticket 会话票据
 * @return {Promise} Promise
 */
// eslint-disable-next-line no-unused-vars
const postSafeOfSecurityLevel1 = (apiUri, params, safeTag, ticket) => {
  let isStr = _.isString(params)
  let _param = isStr ? params : qs.stringify(params)
  return _axios
    .post(apiUri, _param, createSafeConfig(safeTag, isStr ? 2 : 1))
    .then(response => {
      return response
    })
    .catch(error => {
      return error
    })
}

/**
 * 2:防重放+Base64编码+签名
 * @param {string} apiUri api uri
 * @param {ObjectOrJsonString} params 请求参数，对象或json字符串
 * @param {string} safeTag 安全标签, 安全等级;时间戳;防重放攻击的nonce, 例如：2;1551687809382;a411a616d438a...
 * @param {string} ticket 会话票据
 * @return {Promise} Promise
 */
const postSafeOfSecurityLevel2 = (apiUri, params, safeTag, ticket) => {
  let _param = _.isString(params) ? params : JSON.stringify(params)
  // param:Base64编码后的参数, 前置6位混淆随机字符
  let param = randomString(6) + base64.en(_param)
  // strForSign:待签名字符串
  let strForSign = apiUri + param + ticket + safeTag
  // sign:签名
  let sign = sha256(strForSign)
  let requestBody = {
    param,
    sign
  }
  return _axios
    .post(apiUri, qs.stringify(requestBody), createSafeConfig(safeTag))
    .then(response => {
      return response
    })
    .catch(error => {
      return error
    })
}

/**
 * 3:防重放+AES/RSA加密+签名
 * @param {string} apiUri api uri
 * @param {ObjectOrJsonString} params 请求参数，对象或json字符串
 * @param {string} safeTag 安全标签, 安全等级;时间戳;防重放攻击的nonce, 例如：3;1551687809382;a411a616d438a...
 * @param {string} ticket 会话票据
 * @return {Promise} Promise
 */
const postSafeOfSecurityLevel3 = (apiUri, params, safeTag, ticket) => {
  let _param = _.isString(params) ? params : JSON.stringify(params)
  // aesKey:新建AES密钥
  let aesKey = newAESKey()
  // param:AES加密后的参数
  let param = encryptByAES(_param, aesKey)
  // key:RSA公钥加密后的AES密钥
  let key = encryptByRSA(aesKey)
  // strForSign:待签名字符串
  let strForSign = apiUri + param + key + ticket + safeTag
  // sign:签名
  let sign = sha256(strForSign)
  let requestBody = {
    param,
    sign,
    key
  }
  return _axios
    .post(apiUri, qs.stringify(requestBody), createSafeConfig(safeTag))
    .then(response => {
      return response
    })
    .catch(error => {
      return error
    })
}

/**
 * 创建安全标签, 安全等级;时间戳;防重放攻击的nonce, 例如：1;1551687809382;a411a616d438a...
 * @param {int} securityLevel 安全等级[0, 1, 2, 3], 如下：
 *    0:无, 1:防重放, 2:防重放+Base64编码+签名, 3:防重放+AES/RSA加密+签名
 * @return {string} 安全标签
 */
const createSafeTag = securityLevel => {
  if (!securityLevel) {
    return null
  }
  let mix = (Math.random() + '').substr(-5)
  let timestamp = moment() + '-' + mix
  let nonce = md5(mix + ':' + securityLevel + ':' + timestamp)
  return securityLevel + ';' + timestamp + ';' + nonce
}

/**
 * 创建安全axios配置对象
 * @param {string} safeTag 安全标签, 安全等级;时间戳;防重放攻击的nonce, 例如：1;1551687809382;a411a616d438a...
 * @param {int} paramType 参数类型, [1:对象, 2:json字符串, 3:text字符串] 默认为1
 * @return {object} axios.config对象
 */
const createSafeConfig = (safeTag, paramType = 1) => {
  let headers = {}
  let lang = 'en' === localStorage.getItem('lang') ? 'en-US' : 'zh-CN'
  headers['Accept-Language'] = lang
  if (safeTag) {
    headers['SAFE-TAG'] = safeTag
  }
  if (paramType === 2) {
    headers['Content-Type'] = 'application/json;charset=UTF-8'
  } else if (paramType === 3) {
    headers['Content-Type'] = 'text/plain;charset=UTF-8'
  }
  headers['X-Requested-With'] = 'XMLHttpRequest'
  let _config = {
    headers
  }
  return _config
}


/**
 * 下载文件
 * 业务代码中的使用示例：
 * this.$http.download('/common/versionUpdate/download', 'filename.pdf', { aa: 'aaasdf', bb: 1 })
 * @param {String} uri 下载地址
 * @param {String} filename 文件名称
 * @param {Object} params 请求参数
 */
const download = (uri, filename, params) => {
  let userAgent = navigator.userAgent.toLowerCase()
  if (userAgent.indexOf('edge') > -1) {
    // Edge
    downloadGet(uri, params)
    return
  }
  if (userAgent.indexOf('firefox') > -1) {
    // firefox,opera
    downloadSafe(uri, filename, params)
  } else {
    // chrome,IE11,...
    downloadGet(uri, params)
  }
}

/**
 * 下载文件，get方式，无安全标签，以请求参数方式发送token, 文件名称由服务端响应头控制
 * chrome, 360极速, IE11: down OK
 * firefox down failure
 * @param {String} uri 下载地址
 * @param {Object} params 请求参数
 */
const downloadGet = (uri, params) => {
  return new Promise((resolve, reject) => {
    try {
      let iframe = document.createElement('iframe')
      iframe.style.display = 'none'
      if (!params) {
        params = {}
      }
      // get方式下载文件是链接方式，不是ajax方式，加不了请求头，只能通过请求参数添加会话票据
      params.sumTicket = sessionStorage.getItem(TOKEN_NAME)
      let ps = param2String(params)
      if (uri.indexOf('?') !== -1) {
        uri += '&' + ps
      } else {
        if (ps.indexOf('=') !== -1) {
          uri += '?' + ps
        }
      }
      iframe.onload = function () {
        document.body.removeChild(iframe)
      }
      if (uri.indexOf('http') !== 0) {
        iframe.src =
          window.location.protocol + '//' + window.location.host + '/api' + uri
      } else {
        iframe.src = uri
      }
      setTimeout(() => {
        resolve('ok')
      }, 1000)
      window.addEventListener('message', function (error) {
        let res = JSON.parse(error.data)
        if (
          res &&
          typeof res === 'object' &&
          'code' in res &&
          res.code !== 20000
        ) {
          reject(res)
        }
      })
      document.body.appendChild(iframe)
    } catch (e) {
      e
    }
  })
}

/**
 * 将get请求参数转为字符串
 * 示例：{a: 'aa', b: 123} => a=aa&b=123
 * @param {Object/String} params get请求参数
 * @returns {String} 参数字符串
 */
const param2String = params => {
  if (typeof params === 'string') {
    return params
  }
  let ret = ''
  for (let it in params) {
    let val = params[it]
    if (
      typeof val === 'object' && //
      (!(val instanceof Array) ||
        (val.length > 0 && typeof val[0] === 'object'))
    ) {
      val = JSON.stringify(val)
    }
    ret += it + '=' + encodeURIComponent(val) + '&'
  }
  if (ret.length > 0) {
    ret = ret.substring(0, ret.length - 1)
  }
  return ret
}

/**
 * 安全下载，post with token
 * chrome,firefox,opera down OK need filename
 * 360极速(blink内核) down OK need filename
 * IE11 down failure
 * @param {String} uri 下载地址
 * @param {String} filename 文件名称
 * @param {Object} params 请求参数
 */
const downloadSafe = (uri, filename, params) => {
  _axios({
      method: 'post',
      url: uri,
      data: qs.stringify(params),
      headers: {
        'SAFE-TAG': createSafeTag(1)
      },
      // 指定返回服务器返回的数据类型
      responseType: 'blob'
    })
    .then(response => {
      // 处理返回的文件流
      let blob = response
      if (response.type === 'application/json') {
        Message({
          message: '文件下载失败',
          type: 'warnng'
        })
        return
      }
      let reader = new FileReader()
      // 转换为base64，可以直接放入a标签href
      reader.readAsDataURL(blob)
      reader.onload = function (e) {
        let link = document.createElement('a')
        link.download = filename
        link.href = e.target.result
        document.body.appendChild(link)
        link.click()
        // 释放URL对象
        window.URL.revokeObjectURL(link.href)
        // 删除link对象
        document.body.removeChild(link)
      }
    })
    .catch(err => {
      Message({
        message: '文件下载失败: ' + err,
        type: 'warning'
      })
    })
}

/**
 * 内部访问者模式post表单传参请求
 * 例如：postInner('http://localhost:8700/monitor', `/am/amKpi/selectList`, params)
 * @param {String} baseURL 基础地址, 例如：https://192.168.0.165:9700/api
 * @param {String} apiUri api uri 例如：/monitor/polling/base
 * @param {ObjectOrJsonString} params 请求参数，对象或null
 * @return {Promise} Promise
 */
const postInner = (baseURL, apiUri, params = {}) => {
  const axiosIns = axios.create({
    baseURL: baseURL
  })
  let _param = {
    // visitor: bizFolderName,
    secToken: getSecToken()
  }
  if (params) {
    Object.assign(_param, params)
  }
  // let _param = qs.stringify(params)
  return axiosIns
    .post(apiUri, qs.stringify(_param))
    .then(response => {
      return response
    })
    .catch(error => {
      return error
    })
}

/**
 * 内部访问者模式get传参请求
 * 例如：getInner('http://localhost:8700/monitor', `/am/amKpi/selectList`, params)
 * @param {String} baseURL 基础地址, 例如：https://192.168.0.165:9700/api
 * @param {String} apiUri api uri 例如：/monitor/polling/base
 * @param {ObjectOrJsonString} params 请求参数，对象或null
 * @return {Promise} Promise
 */
const getInner = (baseURL, apiUri, params = {}) => {
  const axiosIns = axios.create({
    baseURL: baseURL
  })
  let _param = {
    // visitor: bizFolderName,
    secToken: getSecToken()
  }
  if (params) {
    Object.assign(_param, params)
  }
  let _config = {
    params: _param
  }
  return axiosIns
    .get(apiUri, _config)
    .then(response => {
      return response
    })
    .catch(error => {
      return error
    })
}

/**
 * 获取安全Token, 使用国密SM4加密并Base64编码
 * @return {String} 安全Token
 */
const getSecToken = () => {
  // 获取当前13位毫秒客户端时间戳，例如：1575131281915
  let timeStamp = new Date().getTime()

  // 获取5位随机字符，例如：5aelb
  let random5 = Math.random()
    .toString(36)
    .substring(2, 7)

  // 连接成token，例如：1575131281915-5aelb
  let token = timeStamp + '-' + random5

  // 使用SM4对称加密算法对token使用密钥DEFAULT_SM4_KEY进行加密
  // SM4加密模式为：CBC/Padding
  let encryptToken = encryptBySM4(token)

  // 对密文encryptToken进行Base64编码，便于网络传输
  // 最终获取的secToken就是动态的安全Token
  // window.btoa()方法目前是大部分浏览器(包括IE10+)都支持的Base64编码方法
  return window.btoa(encryptToken)
}

// 统一方法输出口
export {
  post,
  postSafe,
  postJson,
  get,
  getSafe,
  download,
  postInner,
  getInner,
  _axios
}
